#include <stdio.h>
#include <stdlib.h>

#include "udray.h"
#include "glm.h"

bool trace_shadow_ray(Ray *ray, Vect color);

const double TINY = 1e-6;

extern Camera *ray_cam; // camera info
extern int image_i, image_j; // current pixel being shaded
extern bool wrote_image; // has the last pixel been shaded?

// reflection/refraction recursion control

extern int maxlevel; // maximum depth of ray recursion
extern double minweight; // minimum fractional contribution to color
double min_depth;
double max_depth;
double delta_depth;

// these describe the scene

extern vector<GLMmodel *> model_list;
extern vector<Sphere *> sphere_list;
extern vector<Light *> light_list;

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

// intersect a ray with the entire scene (.obj models + spheres)

// x, y are in pixel coordinates with (0, 0) the upper-left hand corner of the image.
// color variable is result of this function--it carries back info on how to draw the pixel

void shade_ray_recursive(int level, double weight, Ray *ray,
		Intersection *inter, Vect color) {
	Surface *surf;
	int i;
	double gray;
	double depth_ratio;

	// initialize color to Phong reflectance model

	shade_ray_local(ray, inter, color);

	// if not too deep, recurse

	if (level + 1 < maxlevel) {

		if (inter->surf->reflectivity * weight > minweight) {
			Ray* ref_ray;
			Vect ref_color = { 0. };

			ref_ray = make_ray();

			reflection_direction(ray->dir, inter->N, ref_ray->dir);
			ref_ray->dir[3] = 0.;
			VectUnit(ref_ray->dir);

			VectAddS(TINY, ref_ray->dir, inter->P, ref_ray->orig);

			trace_ray(level + 1, weight, ref_ray, ref_color);
			VectAddS(inter->surf->reflectivity, ref_color, color, color);

			VectClamp(color, 0, 1);
			free(ref_ray);
			// FILL IN CODE

		}

		// add refraction component to color

		if (surf->transparency * weight > minweight) {

			// GRAD STUDENTS -- FILL IN CODE

		}
	}

	gray = color[R] * .3 + color[G] * .59 + color[B] * .11;
	//	if (inter->t < min_depth)
	//		depth_ratio = 1.;
	//	else if (inter->t > max_depth)
	//		depth_ratio = 0.;
	//	else
	//		depth_ratio = 1 - ((inter->t - min_depth) / delta_depth);
	//	gray *= depth_ratio;

	color[R] = color[G] = color[B] = gray;
}

void trace_ray(int level, double weight, Ray *ray, Vect color) {
	Intersection *nearest_inter = NULL;
	Intersection *inter = NULL;
	int i;

	// test for intersection with all .obj models

	for (i = 0; i < model_list.size(); i++) {
		inter = intersect_ray_glm_object(ray, model_list[i]);
		update_nearest_intersection(&inter, &nearest_inter);
	}

	// test for intersection with all spheres

	for (i = 0; i < sphere_list.size(); i++) {
		inter = intersect_ray_sphere(ray, sphere_list[i]);
		if (inter && inter->t < TINY)
			free(inter);
		else
			update_nearest_intersection(&inter, &nearest_inter);
	}

	// "color" the ray according to intersecting surface properties

	// choose one of the simpler options below to debug or preview your scene more quickly.
	// another way to render faster is to decrease the image size.

	if (nearest_inter) {
		//shade_ray_false_color_normal(nearest_inter, color);
		//    shade_ray_intersection_mask(color);  
		//shade_ray_diffuse(ray, nearest_inter, color);
		shade_ray_recursive(level, weight, ray, nearest_inter, color);
	}

	// color the ray using a default

	else
		shade_ray_background(ray, color);
}

//----------------------------------------------------------------------------

// test for ray-sphere intersection; return details of intersection if true

Intersection *intersect_ray_sphere(Ray *ray, Sphere *S) {
	Vect dP, temp;
	double uDOTdP, disc, magTemp, s;
	Intersection* inter = NULL;

	VectSub(S->P, ray->orig, dP);
	dP[3] = 0.;
	uDOTdP = VectDotProd(ray->dir, dP);
	VectAddS(-uDOTdP, ray->dir, dP, temp);
	temp[3] = 0.;
	magTemp = VectMag(temp);
	disc = SQUARE(S->radius) - SQUARE(magTemp);

	if (disc < 0.)
		return NULL;

	s = min(uDOTdP + sqrt(disc), uDOTdP - sqrt(disc));

	inter = make_intersection();
	VectAddS(s, ray->dir, ray->orig, inter->P);
	inter->P[3] = 1.;

	VectSub(inter->P, S->P, inter->N);
	inter->N[3] = 0.;
	VectUnit(inter->N);

	inter->surf = S->surf;
	inter->t = s;

	return inter;
}

//----------------------------------------------------------------------------

// only local, ambient + diffuse lighting (no specular, shadows, reflections, or refractions)

void shade_ray_diffuse(Ray *ray, Intersection *inter, Vect color) {
	Vect L;
	double diff_factor;
	Ray* shadow_ray;

	// iterate over lights
	shadow_ray = make_ray();

	for (int i = 0; i < light_list.size(); i++) {
		VectSub(light_list[i]->P, inter->P, L);
		L[3] = 0.;
		VectUnit(L);

		VectAddS(TINY, L, inter->P, shadow_ray->orig);
		VectCopy(shadow_ray->dir, L);
		if (!trace_shadow_ray(shadow_ray, color)) {

			diff_factor = VectDotProd(inter->N, L);
			diff_factor = max(diff_factor, 0.);

			// AMBIENT

			color[R] += inter->surf->amb[R] * light_list[i]->amb[R];
			color[G] += inter->surf->amb[G] * light_list[i]->amb[G];
			color[B] += inter->surf->amb[B] * light_list[i]->amb[B];

			// DIFFUSE

			color[R] += inter->surf->diff[R] * light_list[i]->diff[R]
					* diff_factor;
			color[G] += inter->surf->diff[G] * light_list[i]->diff[G]
					* diff_factor;
			color[B] += inter->surf->diff[B] * light_list[i]->diff[B]
					* diff_factor;

			// FILL IN CODE
		}
	}

	// clamp color to [0, 1]

	VectClamp(color, 0, 1);
	free(shadow_ray);
}

//----------------------------------------------------------------------------

bool trace_shadow_ray(Ray *ray, Vect color) {
	//return false;
	Intersection *inter = NULL;
	int i;

	// test for intersection with all .obj models

	for (i = 0; i < model_list.size(); i++) {
		inter = intersect_ray_glm_object(ray, model_list[i]);
		if (inter && inter->t >= TINY) {
			free(inter);
			return true;
		}
	}

	// test for intersection with all spheres

	for (i = 0; i < sphere_list.size(); i++) {
		inter = intersect_ray_sphere(ray, sphere_list[i]);
		if (inter && inter->t >= TINY) {
			free(inter);
			return true;
		}
	}

	return false;
}

// same as shade_ray_diffuse(), but add specular lighting + shadow rays (i.e., full Phong illumination model)

void shade_ray_local(Ray *ray, Intersection *inter, Vect color) {

	Vect H, L, V;
	Ray *shadow_ray;
	double spec_factor;

	// iterate over lights
	shadow_ray = make_ray();

	for (int i = 0; i < light_list.size(); i++) {
		VectSub(light_list[i]->P, inter->P, L);
		L[3] = 0.;
		VectUnit(L);

		VectAddS(TINY, L, inter->P, shadow_ray->orig);
		VectCopy(shadow_ray->dir, L);
		if (!trace_shadow_ray(shadow_ray, color)) {

			VectNegate(ray->dir, V);
			V[3] = 0.;
			VectUnit(V);

			VectAddS(1, L, V, H);
			H[3] = 0.;
			VectUnit(H);

			spec_factor = VectDotProd(H, inter->N);
			spec_factor = pow(spec_factor, inter->surf->spec_exp);

			// SPECULAR

			color[R] += inter->surf->spec[R] * light_list[i]->spec[R]
					* spec_factor;
			color[G] += inter->surf->spec[G] * light_list[i]->spec[G]
					* spec_factor;
			color[B] += inter->surf->spec[B] * light_list[i]->spec[B]
					* spec_factor;

			// FILL IN CODE
		}
	}

	shade_ray_diffuse(ray, inter, color);

	// clamp color to [0, 1]

	VectClamp(color, 0, 1);
	free(shadow_ray);
}

//----------------------------------------------------------------------------

// full shading model: ambient/diffuse/specular lighting, shadow rays, recursion for reflection, refraction

// level = recursion level (only used for reflection/refraction)

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

// ray trace another pixel if the image isn't finished yet

void idle() {
	if (image_j < ray_cam->im->h) {

		raytrace_one_pixel(image_i, image_j);

		image_i++;

		if (image_i == ray_cam->im->w) {
			image_i = 0;
			image_j++;
		}
	}

	// write rendered image to file when done

	else if (!wrote_image) {

		write_PPM("output.ppm", ray_cam->im);

		wrote_image = true;
	}

	glutPostRedisplay();
}

//----------------------------------------------------------------------------

// show the image so far

void display(void) {
	// draw it!

	glPixelZoom(1, -1);
	glRasterPos2i(0, ray_cam->im->h);

	glDrawPixels(ray_cam->im->w, ray_cam->im->h, GL_RGBA, GL_FLOAT,
			ray_cam->im->data);

	glFlush();
}

//----------------------------------------------------------------------------

void init() {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, ray_cam->im->w, 0.0, ray_cam->im->h);
}

//----------------------------------------------------------------------------

int main(int argc, char** argv) {
	glutInit(&argc, argv);

	// initialize scene (must be done before scene file is parsed)

	init_raytracing();

	minweight = 0.25;
	maxlevel = 3;
	min_depth = 0;
	max_depth = 4;

	switch (argc) {
	case 6:
		max_depth = atof(argv[5]);
	case 5:
		min_depth = atof(argv[4]);
	case 4:
		minweight = atof(argv[3]);
	case 3:
		maxlevel = atoi(argv[2]);
	case 2:
		parse_scene_file(argv[1], ray_cam);
		break;
	default:
		printf("missing .scene file\n");
		exit(1);
	}

	delta_depth = max_depth - min_depth;

	// opengl business

	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowSize(ray_cam->im->w, ray_cam->im->h);
	glutInitWindowPosition(500, 300);
	glutCreateWindow("hw3");
	init();

	glutDisplayFunc(display);
	glutIdleFunc(idle);

	glutMainLoop();

	return 0;
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
